"""Controller module"""

import wx
from GridEpistasisView import GridEpistasisView
from gridepi_monitor import gridepi_monitor
import project.project as project
# TODO: fix this import 

import os
import GUIconfig
import sys
#import epiguilib
sys.path.append(GUIconfig.path_to_gridepistasis)
import numpy
from grid_epistasis_pages import full_overview_page
import time 
from GeneFinderUI import GeneFinderUI
import validator
import epiguilib
import threading

PAGE_WIDTH = 800
PAGE_HEIGHT = 600

class OverviewPage(full_overview_page):
    """
    A page the show the selected entities
    """
    
    def __init__(self, parent, size_tuple, genotypes, phenotypes, classes):
        """
        genotypes : a list of genotype names
        phenotypes : a list of phenotype names
        classes : a list of  tuples (<class_type>, <class_values>) 
        """
        
        
        full_overview_page.__init__( self, parent )
        self.SetSize(size_tuple)
        
        self.genotype_list.SetValue(", ".join(genotypes))
        self.phenotype_list.SetValue(", ".join(phenotypes))
        
        self.class_list.SetValue(", ".join([ "%s(%s)"% (x[0], ", ".join(x[1])) for x in classes]))
    
    def OnCloseOverview(self, event):
        self.Destroy()


class GridEpistasis( GridEpistasisView ):
    """
    The epistasis analysis controller UI
    """
        
    def __init__( self, parent, size):
        GridEpistasisView.__init__(self, parent, size)
        self.genotype_lock = threading.Lock()
        #genotypes_in_db = project.extract_genotype_info() #  
        #self.all_genotypes = {}
        #self.all_genotypes.update([(snp[0],(snp[1])) for snp in genotypes_in_db])
        
        self.all_genes = project.extract_genes()
        
        self.genotypes_selection = {} # a gene to snp dictionary for the selected snps
        
        self.all_phenotypes = project.extract_phenotype_names() 
        self.all_phenotypes.sort()
        
        self.phenotypes_selection = {} 
        
        self.all_classes = project.extract_class_names()
        self.classes_selection = {}
        
        self.populate_fields()
        
                
    def populate_fields(self):
        """
        Insert values into each page in listbook
        """
        self.epistasis_listbook.genotypes_page.gene_snp_checklist.Clear()
        
        self.epistasis_listbook.genotypes_page.gene_snp_checklist.add_genes(self.all_genes)
        
        self.epistasis_listbook.phenotypes_page.phenotypes_checklist.Clear()
        self.epistasis_listbook.phenotypes_page.phenotypes_checklist.InsertItems(self.all_phenotypes, 0)
        self.epistasis_listbook.classes_page.classes_checklist.Clear()
        self.epistasis_listbook.classes_page.selection_variable_list.InsertItems(self.all_classes,0)
        
    def update_genotypes_checklist(self):
        """
        Re-checks all the previously checked entries in the newly filtered listing 
        """
        
        listed_items = self.epistasis_listbook.genotypes_page.gene_snp_checklist.GetRootItem().GetChildren()
        
        def f(item):
            return self.genotypes_selection.has_key(item.GetText())
        
        
        checked_items = filter(f, listed_items)
        
        for gene in checked_items:
            gene.Check() # does not autocheck children
                               
        
    def save_selected_genotypes(self, callback=None):
        """
        Saves the currently selected genes and genotypes into the selection dictionary
        """
        callback_update_interval = 20 
        
        self.genotype_lock.acquire()
        all_items = self.epistasis_listbook.genotypes_page.gene_snp_checklist.GetRootItem().GetChildren()
        
        if callback:
            callback(10)
        
        def f(item):
            return item.IsChecked()
        
        checked_genes = filter(f, all_items) 
        
        if callback:
            callback(20)
        
        num_genes = len(checked_genes)
        for i in xrange(num_genes):
            gene = checked_genes[i]
            snps = gene.GetChildren()
            
            checked_snps = []
                
            if snps[0].GetText() != "none": # if the gene has loaded a children snps list 
                for s in snps:
                    if s.IsChecked():
                        checked_snps.append(s.GetText())
            
                gene_snps = [(gene.GetText(), checked_snps)]
                self.genotypes_selection.update(gene_snps)
        
            else:
            # the gene only has empty none entry and the checked gene is not in the selection dictionary so save all the children 
                if not self.genotypes_selection.has_key(gene.GetText()):
                    snps = project.extract_gene_snps(gene.GetText())
                    checked_snps.extend(snps)
                
                    gene_snps = [(gene.GetText(), checked_snps)]
                    self.genotypes_selection.update(gene_snps)
            
            if callback and i % callback_update_interval == 0:
                percentage = int(float(i)/float(num_genes)*100) 
                callback(percentage, "saving %i of %i" % (i, num_genes))
                
        
        self.genotype_lock.release()

    def update_phenotypes_checklist(self):
        """
        Check the newly filtered phenotype list with the saved selection 
        """
        
        listed_items = self.epistasis_listbook.phenotypes_page.phenotypes_checklist.GetItems()
        def f(item):
            return self.phenotypes_selection.has_key(item)
        
        checked_items = filter(f,listed_items)
        self.epistasis_listbook.phenotypes_page.phenotypes_checklist.SetCheckedStrings(checked_items)
    
    def save_selected_phenotypes(self):
        """
        Save the checked phenotypes in the selection dictionary
        """
        
        checked_items = self.epistasis_listbook.phenotypes_page.phenotypes_checklist.GetCheckedStrings()
        self.phenotypes_selection.update([(x,1) for x in checked_items])
        
    def db_data_to_numpy_matrix(self, db_output_list):
        """
        create a numpy matrix and make it column-major
        """
        num_columns = len(db_output_list[0])
        mat = numpy.matrix(db_output_list)
        mat = mat.flatten()
        # TODO: is this necessary?
        mat = mat.reshape(num_columns, -1, order='F') # this means column major 
        
        return mat
        
    def create_start_page_summary(self):
        """
        Populate a summary of the selection items 
        """
        
        MAX_SNPS = 100
        
        self.save_selected_genotypes() # add the currently checked entries to the total selection
        self.save_selected_phenotypes()
        
        genotype_names = self.genotypes_selection.keys()
        phenotype_names = self.phenotypes_selection.keys()
        class_type = self.epistasis_listbook.classes_page.selection_variable_list.GetStringSelection()
        
        classes = self.epistasis_listbook.classes_page.classes_checklist.GetCheckedStrings()
        genotypes_str = ", ".join(genotype_names[:MAX_SNPS])
        if len(genotype_names) > MAX_SNPS:
            genotypes_str += "..."
            
        phenotypes_str = ", ".join(phenotype_names)
        classes_str = "%s(%s)" % (class_type, ", ".join(classes))
        self.epistasis_listbook.start_page.selected_genotypes.SetLabel(genotypes_str)
        self.epistasis_listbook.start_page.selected_genotypes.Wrap(PAGE_WIDTH-100)
        
        self.epistasis_listbook.start_page.selected_phenotypes.SetLabel(phenotypes_str)
        self.epistasis_listbook.start_page.selected_classes.SetLabel(classes_str)
              

    def validate_user_input(self):
        """
        Verify user input
        """

        analysis_name = self.epistasis_listbook.start_page.analysis_name.GetValue()
        
        if not analysis_name:
            return False, "Analysis name is empty." 
        
        result_dir = os.path.join(project.get_current_project().epistasis_output_directory, analysis_name)
        #The path of the result folder: the content of the project settings is loaded by project.get_current_project, and the folder "output_files" is defined; then the folder "analysis_time" is added to the path and here is all results kept
        
        if os.path.exists(result_dir):
            return False, "Analysis name is taken. %s " % result_dir
    
        if not self.genotypes_selection.keys():
            return False, "No genotypes selected."
        
        snps = []
        for gene in self.genotypes_selection.keys():
            for snp in self.genotypes_selection[gene]:
                snps.append(snp)
        
        if len(snps) < 2:
            return False, "At least 2 genotypes are required."
        
        if not self.phenotypes_selection.keys():
            return False, "No phenotypes selected."
        
        classes = self.epistasis_listbook.classes_page.classes_checklist.GetCheckedStrings()
        if not classes:
            return False, "No classes selected."
        
        sign_level_str = self.epistasis_listbook.start_page.significance_level.GetValue()
        try :
            validator.validate_significance_level(sign_level_str) 
         
        except Exception, e:
            return False, e
         
        
        return True, "Validation complete."    
    
    
    def filter_genotypes(self, genotype_matrix, genotype_names):  
        """
        filter monomorphic SNPS. 
        """
        # TODO: this needs to be done before start the jobs
        
        monomorphic_genotypes = []
        
        mono_discard = []#list of itmes (snps) to be deleted from matrix

        for snp_index in xrange(len(genotype_matrix)):
            num_subs = len(genotype_matrix[snp_index])
            for sub_gt in xrange(num_subs):
                if genotype_matrix[snp_index,0] != genotype_matrix[snp_index, sub_gt]:
                        # not monomorph
                        continue         
                if num_subs == sub_gt-1:
                    mono_discard.append(snp_index)
                    monomorphic_genotypes.append(genotype_names[snp_index])
        
        filtered_matrix = numpy.delete(genotype_matrix, mono_discard)
        
        return filtered_matrix, monomorphic_genotypes
    
    
    def create_analysis_data(self, genotypes, phenotypes, class_type, classes):
        genotype_files = []
        phenotype_files = []
        
        for c in classes: 
        
            genotype_data = project.extract_genotypes_for_single_class_type(genotypes, class_type, [c]) 
            phenotype_data = project.extract_phenotypes_for_single_class_type(phenotypes, class_type, [c])
            
            genotype_matrix = self.db_data_to_numpy_matrix(genotype_data)
            phenotype_matrix = self.db_data_to_numpy_matrix(phenotype_data)
        
            genotype_matrix_file = "genotype_matrix_class%s.dat" % c
            phenotype_matrix_file = "phenotype_matrix_class%s.dat" % c
        
            genotype_matrix.tofile(genotype_matrix_file, sep=" ")
            phenotype_matrix.tofile(phenotype_matrix_file, sep=" ")  
            genotype_files.append(genotype_matrix_file)
            phenotype_files.append(phenotype_matrix_file)
        return genotype_files, phenotype_files
    
    
    def start_monitor(self, args):
        """
        Start the monitor page and remove the epistasis analysis page.
        """
        
        monitor_page = gridepi_monitor(epi_args=args, parent=self.GetTopLevelParent())
        
        if self == self.GetTopLevelParent(): # just for testing
            #print "im top level"
            monitor_page.Show()
        else:
            self.GetTopLevelParent().add_page_tab(monitor_page, "Epistasis monitor")
            self.GetTopLevelParent().remove_page_tab(self)

        
#---------------------------------
#-------- Event handlers ------------
#-----------------------------------         
    ## Genotypes page events ##########
    
    def OnGenotypeClicked( self, event):
        entry = event.GetItem()
        
        if not entry.HasChildren(): # this is snp and not a gene
            gene = entry.GetParent()
                        
            # if a snp has been checked then check the parent gene as well. does not fire EVT_TREE_ITEM_CHECKED
            self.epistasis_listbook.genotypes_page.gene_snp_checklist.CheckItem2(gene, True, torefresh=True) # torefresh makes sure to refresh the screen 
        
        
    def OnGenotypesFilterText( self, event ):
        """
        Fires when a new string is written in the search field. Save the current selection and show the filtered list 
        """
        
        self.save_selected_genotypes()
        filter_str = self.epistasis_listbook.genotypes_page.genotypes_filter.GetValue()
        
        def f(value):
            
            return (value.lower().find(filter_str.lower()) != -1)
        
        filtered_list = filter(f, self.all_genes)
        self.epistasis_listbook.genotypes_page.gene_snp_checklist.Clear()
        self.epistasis_listbook.genotypes_page.gene_snp_checklist.add_genes(filtered_list)
        self.update_genotypes_checklist() 
    
    
    def OnStartGeneFinder(self, event):
        dlg = wx.MessageDialog(self, 'This will update the gene database. Close the grid analysis page and start gene finder?', 'Please Confirm', wx.YES_NO |
                               wx.YES_DEFAULT | wx.ICON_QUESTION)        
        if dlg.ShowModal() != wx.ID_YES:
            return False
        
        gf = GeneFinderUI(self.Parent)
        gf.Show()
        
        self.GetTopLevelParent().remove_page_tab(self)
        
        
    def OnGenotypesCheckall( self, event ):
        
        root = self.epistasis_listbook.genotypes_page.gene_snp_checklist.GetRootItem()
        self.epistasis_listbook.genotypes_page.gene_snp_checklist.CheckChilds(root)
        
            
    def OnGenotypesUncheckall( self, event ):
        """
        Since we only save the checked entries in save_selected_genotypes, on uncheck we need to remove the the current selection from dictionary. 
        """
        listed_items = self.epistasis_listbook.genotypes_page.gene_snp_checklist.GetRootItem().GetChildren()
        
        for gene in listed_items:
            name = gene.GetText()
            if self.genotypes_selection.has_key(name): # remove the genes from the selection dictionary
                del self.genotypes_selection[name]
        
        root = self.epistasis_listbook.genotypes_page.gene_snp_checklist.GetRootItem()
        self.epistasis_listbook.genotypes_page.gene_snp_checklist.CheckChilds(root, False)
        
        
    def OnGeneExpanding(self, event):
        """
        Fires when expanding a gene entry. Looks up the gene in the DB and adds the snps entries.
        """
        
        gene_entry = event.GetItem()
        gene_name = gene_entry.GetText()
        first_child,_ = self.epistasis_listbook.genotypes_page.gene_snp_checklist.GetFirstChild(gene_entry)
        
        if first_child.GetText() != "none": # only populate the first time when it is empty!
            return
        
        snps = project.extract_gene_snps(gene_name)
        self.epistasis_listbook.genotypes_page.gene_snp_checklist.add_snps(snps, gene_entry)
            
        if gene_entry.IsChecked():
            
            if self.genotypes_selection.has_key(gene_name) and self.genotypes_selection[gene_name][0] != "none":
                
            # only check the snps that have been specifically chosen.
            
                checked_snps = self.genotypes_selection[gene_entry.GetText()]
            
                for c in gene_entry.GetChildren():
                    if c.GetText() in checked_snps: 
                        self.epistasis_listbook.genotypes_page.gene_snp_checklist.CheckItem(c)
            else:
                self.epistasis_listbook.genotypes_page.gene_snp_checklist.CheckChilds(gene_entry)
       
    ### Phenotypes page events #########
    
    
    def OnPhenotypesFilterText( self, event ):
        self.save_selected_phenotypes()        
        
        names = self.all_phenotypes
        filter_str = self.epistasis_listbook.phenotypes_page.phenotypes_filter.GetValue()
        
        def f(namestr):
            return (namestr.find(filter_str) != -1)
        
        filtered_list = filter(f, names)
        self.epistasis_listbook.phenotypes_page.phenotypes_checklist.Clear()
        self.epistasis_listbook.phenotypes_page.phenotypes_checklist.InsertItems(filtered_list, 0)
        self.update_phenotypes_checklist()
    
    
    def OnPhenotypesCheckall( self, event ):
        selected = self.epistasis_listbook.phenotypes_page.phenotypes_checklist.GetItems()
        self.epistasis_listbook.phenotypes_page.phenotypes_checklist.SetCheckedStrings(selected)

            
    def OnPhenotypesUncheckall( self, event ):
        unselected = self.epistasis_listbook.phenotypes_page.phenotypes_checklist.GetItems()
        for us in unselected:
            if self.phenotypes_selection.has_key(us):
                del self.phenotypes_selection[us]
        
        self.epistasis_listbook.phenotypes_page.phenotypes_checklist.SetCheckedStrings([])


    ### Class page events ########
    def OnClassesCheckall( self, event ):
        selected = self.epistasis_listbook.classes_page.classes_checklist.GetItems()
        self.epistasis_listbook.classes_page.classes_checklist.SetCheckedStrings(selected)

            
    def OnClassesUncheckall( self, event ):
        unselected = self.epistasis_listbook.classes_page.classes_checklist.GetItems()
        for us in unselected:
            if self.classes_selection.has_key(us):
                del self.classes_selection[us]
        
        self.epistasis_listbook.classes_page.classes_checklist.SetCheckedStrings([])
        
            
    def OnSelectionVariable(self, event):
        class_name = event.GetEventObject().GetStringSelection()
        #print "on selection variable"
        
        if not class_name: # for some reason this event fires even though nothing is selected
            #print "empty class name value selected. Can't look up.."
            return
        
        class_data = project.extract_distinct_classes([class_name])
        class_data.sort()
        class_data = [str(int(c[0])) for c in class_data]
        if class_data.count("-1"):
            class_data.remove("-1")
                
        self.epistasis_listbook.classes_page.classes_checklist.Clear()
        self.epistasis_listbook.classes_page.classes_checklist.InsertItems(class_data,0)
    
    
    ### Start analysis page events ##########
    
    def OnPageChanged(self, event):
        """
        This fires when the page has changed
        """
        
        current_page = self.epistasis_listbook.GetCurrentPage()
        
        if current_page == self.epistasis_listbook.start_page: # when entering start analysis page
            self.create_start_page_summary()
            
            default_name = "analysis_"+time.asctime().replace(" ", "_").replace(":","")
            self.epistasis_listbook.start_page.analysis_name.SetValue(default_name)
    
    
    def OnPageChanging(self, event):
        """
        This fires before the page has changed
        """
        
        current_page = self.epistasis_listbook.GetCurrentPage()
        
        if current_page == self.epistasis_listbook.genotypes_page: # when leaving geontypes page
            epiguilib.run_in_thread_with_progressbar(self.save_selected_genotypes, (), None, self, "Saving genoypes selection")
            
        
            
    def OnFullOverview(self, event):
        """
        Creates a detailed overview of the selected items
        """
        self.save_selected_genotypes() # add the currently checked entries to the total selection
        self.save_selected_phenotypes()
        
        size = (PAGE_WIDTH,PAGE_HEIGHT)
        class_type = self.epistasis_listbook.classes_page.selection_variable_list.GetStringSelection()
        
        classes = self.epistasis_listbook.classes_page.classes_checklist.GetCheckedStrings()
        
        class_list = [(class_type, classes)]
        
        
        overview_page = OverviewPage(self, size, self.genotypes_selection.keys(), self.phenotypes_selection.keys(), class_list)
        overview_page.Show()
       
        
    def OnCancel( self, event ):
        self.GetTopLevelParent().remove_page_tab(self)
        
    def OnStartAnalysis( self, event ):
        """
        Starts the the epistasis analysis backend
        """
        
        self.save_selected_genotypes() # add the currently checked entries to the total selection
        self.save_selected_phenotypes()
        
        input_validated, msg = self.validate_user_input()
        if not input_validated:
            # TODO : does this work?
            wx.MessageBox(msg)
            return False
        
        self.Enable(False)
        analysis_name = self.epistasis_listbook.start_page.analysis_name.GetValue()
        
        snp_gene_names = []
        snps = []
        for gene in self.genotypes_selection.keys():
            for snp in self.genotypes_selection[gene]:
                snp_gene_names.append((snp,gene))
                snps.append(snp)
        print snps, len(snps), snp_gene_names
        
        phenotype_names = self.phenotypes_selection.keys()
        class_type = self.epistasis_listbook.classes_page.selection_variable_list.GetStringSelection()
        classes = self.epistasis_listbook.classes_page.classes_checklist.GetCheckedStrings()
        
        # TODO: filter genotypes and phenotypes to avoid it later in the jobs
        genotype_files, phenotype_files = self.create_analysis_data(snps, phenotype_names, class_type, classes)
        
#        genotype_data = project.extract_genotypes_for_single_class_type(snps, class_type, classes)
#        phenotype_data = project.extract_phenotypes_for_single_class_type(phenotype_names, class_type, classes)
#        
#        genotype_matrix = self.db_data_to_numpy_matrix(genotype_data)
#        
#        phenotype_matrix = self.db_data_to_numpy_matrix(phenotype_data)
#        
#        genotype_matrix_file = "genotype_matrix.dat"
#        phenotype_matrix_file = "phenotype_matrix.dat"
#        
#        genotype_matrix.tofile(genotype_matrix_file, sep=" ")
#        phenotype_matrix.tofile(phenotype_matrix_file, sep=" ")  
#        
        local_mode = self.epistasis_listbook.start_page.local_mode_checkbox.GetValue()
        bonferoni_correction = self.epistasis_listbook.start_page.bonferoni_checkbox.GetValue()
        significance_level = float(self.epistasis_listbook.start_page.significance_level.GetValue())
        
        #print snp_gene_names
        #print genotype_matrix, genotype_matrix.shape
        
        #print phenotype_names, phenotype_matrix.shape
        #print phenotype_matrix
        
        result_dir = os.path.join(project.get_current_project().epistasis_output_directory, analysis_name)
        #The path of the result folder: the content of the project settings is loaded by project.get_current_project, and the folder "output_files" is defined; then the folder "analysis_time" is added to the path and here is all results kept

        status_file_name = analysis_name + GUIconfig.status_file_extension 
        status_file = os.path.join(project.get_current_project().epistasis_status_directory, status_file_name) 
        
        epistasis_args = {"class_type" : class_type, "classes" : classes, "snp_gene_names" : snp_gene_names, 
                     "genotype_files" : genotype_files, "phenotype_names" : phenotype_names, "phenotype_files" : phenotype_files, 
                     "analysis_name" : analysis_name, "local_mode" : local_mode, "status_file" : status_file, 
                     "result_dir" : result_dir, "bonferoni_correction" : bonferoni_correction, "significance_level" : significance_level}
        
        #args = (class_type, classes, snp_gene_names, genotype_files, phenotype_names, phenotype_files, analysis_name, local_mode, status_file, result_dir)
#        print args
        
        self.start_monitor(epistasis_args)
        

if __name__ == "__main__":
    app = wx.App(False)  # Create a new app, don't redirect stdout/stderr to a window.
    project.open_project("/home/benjamin/Dokumenter/epiprojects/my_project_testshit/my_project_testshit.epiprj")
    frame = wx.Frame(None, wx.ID_ANY, "Hello World", size=(1000,800)) # A Frame is a top-level window.
    
    #panel = wx.Panel(frame, wx.ID_ANY) # A Frame is a top-level window.)
    

    w = GridEpistasis(frame, (800,600))
    frame.Show()
    
    #w = GridEpistasis(parent=None) # A Frame is a top-level window.
    #w.Show(True)     # Show the frame.
    app.MainLoop()

    project.close_current_project()
    
